package com.monolith.jd.demo.common.util;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * Created by wuzuxiang on 2018/7/3.
 */
public class HMacSignatureBuilder {
    private static final String ALGORITHM_HMACSHA512 = "HmacSHA512";
    private static final String DMS_SIGNATURE_SIGN = "DMS_SIGNATURE_SIGN";

    private static final String HEADER_CONTENT_TYPE = "Content-Type";
    private static final String HEADER_REGISTER_NO = "RegisterNo";
    private static final String HEADER_SIGNATURE = "Signature";
    private static final String HEADER_AUTHORIZATION = "Authorization";
    private static final String HEADER_SITECODE = "SiteCode";
    private static final String HEADER_OPCODE = "OpCode";
    private static final String HEADER_DATE = "Date";

    private static final byte WRAP = '\n';
    private String signature = DMS_SIGNATURE_SIGN;
    private String scheme;
    private String host;
    private String method;
    private String resource;
    private String contentType;
    private String siteCode;
    private String registerNo;
    private String opCode;
    private String date;
    private byte[] content;

    public HMacSignatureBuilder signature(String signature) {
        this.signature = signature;
        return this;
    }

    public HMacSignatureBuilder scheme(String scheme) {
        this.scheme = scheme;
        return this;
    }

    public HMacSignatureBuilder host(String host) {
        this.host = host;
        return this;
    }

    public HMacSignatureBuilder method(String method) {
        this.method = method;
        return this;
    }

    public HMacSignatureBuilder resource(String resource) {
        this.resource = resource;
        return this;
    }

    public HMacSignatureBuilder contentType(String contentType) {
        this.contentType = contentType;
        return this;
    }

    public HMacSignatureBuilder siteCode(String siteCode) {
        this.siteCode = siteCode;
        return this;
    }

    public HMacSignatureBuilder registerNo(String registerNo) {
        this.registerNo = registerNo;
        return this;
    }

    public HMacSignatureBuilder opCode(String opCode) {
        this.opCode = opCode;
        return this;
    }

    public HMacSignatureBuilder date(String date) {
        this.date = date;
        return this;
    }

    public HMacSignatureBuilder content(byte[] content) {
        this.content = content;
        return this;
    }

    public byte[] build() {
        Objects.requireNonNull(signature, "signature must be not null");
        Objects.requireNonNull(scheme, "scheme must be not null");
        Objects.requireNonNull(host, "host must be not null");
        Objects.requireNonNull(method, "method must be not null");
        Objects.requireNonNull(resource, "resource must be not null");
        Objects.requireNonNull(siteCode, "siteCode must be not null");
        Objects.requireNonNull(registerNo, "registerNo must be not null");
        Objects.requireNonNull(opCode, "opCode must be not null");
        Objects.requireNonNull(date, "date must be not null");
        Objects.requireNonNull(content, "content must be not null");
        try {
            final Mac digest = Mac.getInstance(ALGORITHM_HMACSHA512);
            SecretKey key = new SecretKeySpec(registerNo.getBytes(), ALGORITHM_HMACSHA512);
            digest.init(key);
            digest.update(signature.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(scheme.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(host.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(method.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(resource.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(contentType.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(siteCode.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(registerNo.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(opCode.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(date.getBytes(StandardCharsets.UTF_8));
            digest.update(WRAP);
            digest.update(content);
            digest.update(WRAP);
            final byte[] signatureByte = digest.doFinal();
            digest.reset();
            return signatureByte;
        } catch (Exception e) {
            throw new RuntimeException("Can't create signature: " + e.getMessage(), e);
        }
    }

    public boolean isHashEquals(byte[] expectedSignature) {
        final byte[] signature = build();
        return MessageDigest.isEqual(signature, expectedSignature);
    }

    public String buildAsHexString() {
        return DatatypeConverter.printHexBinary(build());
    }

    public String buildAsBase64String() {
        byte[] bytes = build();
        return DatatypeConverter.printBase64Binary(bytes);
    }

    public Map<String, String> buildHeaders() {
        Map<String, String> headers = new HashMap<>(7);
        headers.put(HEADER_CONTENT_TYPE, contentType);
        headers.put(HEADER_REGISTER_NO, registerNo);
        headers.put(HEADER_AUTHORIZATION, buildAsBase64String());
        headers.put(HEADER_SIGNATURE, signature);
        headers.put(HEADER_DATE, date);
        headers.put(HEADER_SITECODE, siteCode);
        headers.put(HEADER_OPCODE, opCode);
        return headers;
    }
}
